package org.eraser.gateway.dynamic;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@Slf4j
@Component
@DependsOn({"org.eraser.gateway.dynamic.ListeningProperties"})
public class GatewayDynamicRouteWorker {


    /**
     * nacos 动态配置原理
     * {@see https://www.cnblogs.com/taojietaoge/p/16638226.html}
     * <p></p>
     * nacos 的 ConfigService 默认实现 NacosConfigService
     * <p></p>
     * 1.其构造方法中创建了一个 ClientWorker:
     * <pre>
     *  this.worker = new ClientWorker(this.configFilterChainManager,
     *                                 serverListManager,
     *                                 clientProperties);
     * </pre>
     * 2.ClientWorker 中构建了一个 ConfigRpcTransportClient agent 对象建立和维护与 Nacos 服务器的连接
     * <p></p>
     * 如下:
     * <pre>
     * agent = new ConfigRpcTransportClient(properties, serverListManager);
     * int count = ThreadUtils.getSuitableThreadCount(THREAD_MULTIPLE);
     * ScheduledExecutorService executorService =
     *     Executors.newScheduledThreadPool(
     *          Math.max(count, MIN_THREAD_NUM),
     *          r -> {
     *              Thread t = new Thread(r);
     *              t.setName("com.alibaba.nacos.client.Worker");
     *              t.setDaemon(true);
     *              return t;
     *          }
     *     );
     * agent.setExecutor(executorService);
     * agent.start();
     * </pre>
     * 3.ConfigRpcTransportClient 是 ConfigTransportClient 的实现
     * <pre>
     * // 内部包装了一个单线程的线程池 其 start() 方法如下:
     * public void start() throws NacosException {
     *      securityProxy.login(this.properties);
     *      this.executor.scheduleWithFixedDelay
     *      (
     *          () -> securityProxy.login(properties),
     *                0,
     *                this.securityInfoRefreshIntervalMills,
     *                TimeUnit.MILLISECONDS
     *      );
     *      startInternal();
     * }
     * </pre>
     * 4.start() 方法内部调用了 startInternal() 方法 其内容如下:
     * <pre>
     * executor.schedule(() -> {
     *     while (!executor.isShutdown() && !executor.isTerminated()) {
     *          try {
     *              listenExecutebell.poll(5L, TimeUnit.SECONDS);
     *              if (executor.isShutdown() || executor.isTerminated()) {
     *                  continue;
     *              }
     *              executeConfigListen();
     *          } catch (Throwable e) {
     *              LOGGER.error(
     *                  "[ rpc listen execute ] [rpc listen] exception",
     *                  e
     *              );
     *          }
     *     }
     * }, 0L, TimeUnit.MILLISECONDS);
     *
     * </pre>
     * 5.executeConfigListen();
     * <p></p>
     * 6.refreshContentAndCheck(CacheData cacheData, boolean notify)
     */
    private ConfigService configService;

    private final GatewayRouteService dynamicRouteService;

    @Autowired
    public GatewayDynamicRouteWorker(GatewayRouteService dynamicRouteService) {
        this.dynamicRouteService = dynamicRouteService;
    }

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * Bean在容器中构造完成之后会执行 init 方法
     */
    @PostConstruct
    public void init() {
        log.info("gateway route init ....");

        try {
            //初始化 Nacos 配置客户端
            initConfigService();
            if (null == configService) {
                log.error("init NacosConfigService fail");
                return;
            }

            // 项目启动 从nacos获取路由配置
            String configInfo = configService.getConfig(
                    ListeningProperties.NACOS_ROUTE_DATA_ID,
                    ListeningProperties.NACOS_ROUTE_GROUP,
                    ListeningProperties.DEFAULT_TIMEOUT
            );
            log.info("get current gateway route config from nacos: {}", "\n" + configInfo);

            List<RouteDefinition> definitionList = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
            });

            if (CollectionUtils.isNotEmpty(definitionList)) {
                // 路由配置检测
                for (RouteDefinition definition : definitionList) {
                    // 验证路由格式是否合法
                    if (!StringUtils.hasLength(definition.getId())) {
                        throw new RuntimeException("路由id不能为空");
                    }
                }

                for (RouteDefinition definition : definitionList) {
                    log.info("init gateway route config: {}", definition.toString());
                    dynamicRouteService.addRouteDefinition(definition);
                }
            }

        } catch (Exception ex) {
            log.error("gateway route init has some error: {}", ex.getMessage(), ex);
            throw new RuntimeException(ex);
        }

        // 项目启动 添加一个监听器 监听指定配置文件的更新
        dynamicRouteByNacosListener(ListeningProperties.NACOS_ROUTE_DATA_ID, ListeningProperties.NACOS_ROUTE_GROUP);
    }


    private void initConfigService() {
        try {
            Properties properties = new Properties();
            properties.setProperty("serverAddr", ListeningProperties.NACOS_SERVER_ADDR);
            properties.setProperty("namespace", ListeningProperties.NACOS_NAMESPACE);

            properties.setProperty("group", ListeningProperties.NACOS_ROUTE_GROUP);
            properties.setProperty("dataId", ListeningProperties.NACOS_ROUTE_DATA_ID);
            configService = NacosFactory.createConfigService(properties);
        } catch (Exception ex) {
            log.error("init NacosConfigService error: {}", ex.getMessage(), ex);
            throw new RuntimeException(ex);
        }
    }


    private void dynamicRouteByNacosListener(String dataId, String group) {
        try {
            configService.addListener(dataId, group, new Listener() {

                /**
                 * 自己提供线程池执行操作
                 */
                @Override
                public Executor getExecutor() {
                    // 顺序执行所有任务 异步执行(全量更新路由配置)
                    // new LinkedBlockingQueue<>(); 无界队列 更新网关路由配置
                    return Executors.newSingleThreadExecutor();
                }

                /**
                 * 监听器收到配置更新
                 * @param rotesInfo Nacos 中最新的配置定义
                 */
                @SneakyThrows
                @Override
                public void receiveConfigInfo(String rotesInfo) {
                    log.info("Start to update config: {}", "\n" + rotesInfo);
                    List<RouteDefinition> definitionList = objectMapper.readValue(rotesInfo, new TypeReference<List<RouteDefinition>>() {});
                    dynamicRouteService.updateList(definitionList);
                    log.info("The updated route configuration is complete: {}", definitionList.toString());
                }
            });
        } catch (Throwable ex) {
            log.error("dynamic update gateway config error: {}", ex.getMessage(), ex);
        }
    }

}
